#include "stdafx.h"
#include "XStringBundleMgrImpl.h"

#include "XStrUtil.h"
#include "Util.h"
#include "FileUtil.h"
#include "IXFileSystem.h"
#include "XPtr.h"
#include "UIDefaultValue.h"
#include "IXConfigFile.h"
#include "IXEnumConfigNode.h"

IMPLEMENT_SKIN_CLASS(CXStringBundleMgrImpl);

#define CONFIG_STRING_NODE_NAME             "string"
#define CONFIG_STRING_NODE_ID_ATTR_NAME     "id"
#define CONFIG_STRING_NODE_VALUE_ATTR_NAME  "value"

CXStringBundleMgrImpl::CXStringBundleMgrImpl()
{
    m_strCurrentLang = XDEFAULT_SKIN_LANGUAGE;
    m_strBundlePath = FileUtil::CombinePath(XDEFAULT_SKIN_STRING_BUNDLE_ROOT, XDEFAULT_SKIN_LANGUAGE);
}

CXStringBundleMgrImpl::~CXStringBundleMgrImpl()
{
    ReleaseData();
}

// IStringBundleMgr
HRESULT CXStringBundleMgrImpl::InitStringBundleMgr(IN LPCTSTR szSkinRootPath, IN LPCTSTR szLanguage)
{
    HRESULT hResult = E_FAIL;
    m_strSkinConfigPath = szSkinRootPath;

    XString strLang;
    if(szLanguage == NULL || szLanguage[0] == 0)
        strLang = XDEFAULT_SKIN_LANGUAGE;
    else
        strLang = szLanguage;

    // Read From Config
    XString strBundlePath;
    do 
    {
        XInstPtr<IXConfigFile> pConfig;
        if(pConfig == NULL)
            break;

        if(FAILED(pConfig->put_FileXNC(szSkinRootPath)))
            break;

        XPtr<IXConfigNode> pConfigNode;
        if(FAILED(pConfig->GetNode(_T("\\StringBundle"), &pConfigNode)) || pConfigNode == NULL)
            break;

        XPtr<IXEnum> pEnumStub;
        if(FAILED(pConfigNode->EnumNode(_T("\\"), _T("bundle"), &pEnumStub)) || pEnumStub == NULL)
            break;

        XBSTR bstrLang;
        XQIPtr<IXEnumConfigNode> pEnum = (IXEnum*)pEnumStub;
        XPtr<IXConfigNode> pNode = NULL;
        while(S_OK == (pEnum->Next(&pNode)))
        {
            if(SUCCEEDED(pNode->GetValue(_T("lang"), &bstrLang))
                && (szLanguage == NULL || _tcscmp(szLanguage, bstrLang) == 0))
            {
                XBSTR bstrPath;
                if(SUCCEEDED(pNode->GetValue(_T("path"), &bstrPath)))
                {
                    if(IsLangExists(bstrLang, bstrPath))
                    {
                        m_strCurrentLang = bstrLang;
                        strBundlePath = bstrPath;
                        hResult = S_OK;
                        break;
                    }
                }
            }
            pNode = NULL;
        }

    } while (FALSE);

    XASSERT_ERROR(SUCCEEDED(hResult), _T("Failed to Init StringBundle"));
    if(hResult == S_OK)
    {
        m_strBundlePath = strBundlePath;
    }
    if(m_strBundlePath.size() > 0 && m_strBundlePath[m_strBundlePath.size() - 1] == _T('\\'))
        m_strBundlePath.resize(m_strBundlePath.size() - 1);

    return hResult;
}

HRESULT CXStringBundleMgrImpl::LoadString(OUT BSTR* pbstr, IN LPCTSTR szBundleNames)
{
    HRESULT hResult = E_INVALIDARG;
    if(szBundleNames == NULL)
        return hResult;
    do 
    {
        if(szBundleNames[0] != _T('@'))
            break;

        XString strBundleName(szBundleNames);
        size_t pos = strBundleName.find(_T(':'));
        XASSERT(ASSERT_LEVEL_WARNING, pos != XString::npos, _T("Bad StringId in CXStringBundleMgrImpl::LoadString"));
        if(pos == XString::npos)
            break;

        strBundleName = XStrUtil::ToLower(strBundleName);
        XString strName = strBundleName.substr(1, pos - 1);
        StringBundleMap::iterator IteBundle = m_StringBundleMap.find(strName);
        if(IteBundle == m_StringBundleMap.end())
        {
            if(FAILED(ParseBundleFile(strName.GetData())))
                break;
            IteBundle = m_StringBundleMap.find(strName);
            if(IteBundle == m_StringBundleMap.end())
                break;
        }

        strName = strBundleName.substr(pos + 1);

        StringMap::iterator IteString = IteBundle->second.find(strName);
        if(IteString == IteBundle->second.end())
            break;

        hResult = (::SysReAllocString(pbstr, IteString->second.GetData()) ? S_OK : E_FAIL);
    } while (FALSE);

    XASSERT_WARNING(SUCCEEDED(hResult), _T("LoadString Failed"));

    return hResult;
}

HRESULT CXStringBundleMgrImpl::EnumString(IN OUT DWORD pos, OUT BSTR* bstrId, OUT BSTR* bstrValue)
{
    if(pos >= m_StringBundleMap.size())
        return E_INVALIDARG;

    DWORD i = 0;
    StringMap::iterator IteString;
    StringBundleMap::iterator IteBundle = m_StringBundleMap.begin();
    for(; IteBundle != m_StringBundleMap.end(); ++ IteBundle)
    {
        IteString = IteBundle->second.begin();
        for(; IteString != IteBundle->second.end(); ++ IteString)
        {
            if(i >= pos)
                break;
            ++ i;
        }
    }

    if(i < pos)
        return E_INVALIDARG;

    const XString& refId = IteString->first;
    const XString& refValue = IteString->second;

    if(::SysReAllocString(bstrId, refId.GetData()))
    {
        if(::SysReAllocString(bstrValue, refValue.GetData()))
            return S_OK;
    }
    return E_FAIL;
}

void CXStringBundleMgrImpl::OnFinalRelease()
{
    superclass::OnFinalRelease();
    ReleaseData();
}

void CXStringBundleMgrImpl::ReleaseData()
{
    m_StringBundleMap.clear();
}

HRESULT CXStringBundleMgrImpl::ParseBundleFile(LPCTSTR szBundleName)
{
    char* pBuffer = NULL;
    DWORD dwSize = 0;

    XPtr<IXFixedBuffer> pBundle = NULL;
    XInstPtr<IXFileSystem> pFileSystem;
    if(pFileSystem == NULL)
        return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);

    XString strXNC = m_strBundlePath;
    strXNC += _T("\\");
    strXNC += szBundleName;
    strXNC += _T(".xml");
    HRESULT hResult = pFileSystem->LoadFile(strXNC.GetData(), &pBundle);
    if(FAILED(hResult))
        return hResult;

    hResult = pBundle->get_Data((LPCVOID*)&pBuffer, &dwSize);
    if(FAILED(hResult))
        return hResult;

    std::string strXml;
    strXml.assign(pBuffer, dwSize);

    StringMap m;
    XString strBundleName(szBundleName);
    strBundleName = FileUtil::ExtractFileName(szBundleName);
    strBundleName = XStrUtil::ToLower(strBundleName);
    std::pair<StringBundleMap::iterator, bool> result = m_StringBundleMap.insert(StringBundleMap::value_type(strBundleName.GetData(), m));
    if(!result.second)
        return E_INVALIDARG;

    TiXmlDocument doc;
    doc.Parse(strXml.c_str(), NULL, TIXML_ENCODING_UTF8);
    if(doc.Error())
        return E_INVALIDARG;

    XString strId, strValue;

    StringMap& bundle = (result.first->second);

    TiXmlElement* pRoot = doc.RootElement();
    TiXmlElement* pString = pRoot->FirstChildElement(CONFIG_STRING_NODE_NAME);
    for(; pString != NULL; pString = pString->NextSiblingElement(CONFIG_STRING_NODE_NAME))
    {
        const char* pId = pString->Attribute(CONFIG_STRING_NODE_ID_ATTR_NAME);
        const char* pValue = pString->Attribute(CONFIG_STRING_NODE_VALUE_ATTR_NAME);

        if(pId == NULL || pId[0] == 0 || pValue == NULL)    // Allow value is empty string.
        {
            continue;
        }

        strId = XStrUtil::Utf8ToStr(pId);
        strId = XStrUtil::ToLower(strId);

        strValue = XStrUtil::Utf8ToStr(pValue);

        bundle.insert(StringMap::value_type(strId, strValue));
    }

    return S_OK;
}


HRESULT CXStringBundleMgrImpl::put_Lang(LPCTSTR szLang)
{
    XString strBundlePath;
    HRESULT hResult = E_FAIL;
    do 
    {
        XInstPtr<IXConfigFile> pConfig;
        if(pConfig == NULL)
            break;

        if(FAILED(pConfig->put_FileXNC(m_strSkinConfigPath)))
            break;

        XPtr<IXConfigNode> pConfigNode;
        if(FAILED(pConfig->GetNode(_T("\\StringBundle"), &pConfigNode)) || pConfigNode == NULL)
            break;

        XPtr<IXEnum> pEnumStub;
        if(FAILED(pConfigNode->EnumNode(_T("\\"), _T("bundle"), &pEnumStub)) || pEnumStub == NULL)
            break;

        XBSTR bstrLang;
        XQIPtr<IXEnumConfigNode> pEnum = (IXEnum*)pEnumStub;
        XPtr<IXConfigNode> pNode = NULL;
        while(S_OK == (pEnum->Next(&pNode)))
        {
            if(SUCCEEDED(pNode->GetValue(_T("lang"), &bstrLang)) && bstrLang == szLang)
            {
                break;
            }
            pNode = NULL;
        }

        hResult = E_FAIL;
        if(pNode != NULL)
        {
            XBSTR bstrPath;
            if(SUCCEEDED(pNode->GetValue(_T("path"), &bstrPath)) && IsLangExists(bstrLang, bstrPath))
            {
                m_strCurrentLang = szLang;
                m_StringBundleMap.clear();
                m_strBundlePath = bstrPath;
                hResult = S_OK;
            }
        }
    } while (FALSE);

    return hResult;
}

HRESULT CXStringBundleMgrImpl::get_Lang(OUT BSTR* pbstrLang)
{
    if(::SysReAllocString(pbstrLang, m_strCurrentLang.GetData()))
        return S_OK;
    else
        return E_FAIL;
}

BOOL CXStringBundleMgrImpl::IsLangExists(LPCTSTR szLang, LPCTSTR szPath)
{
    XInstPtr<IXFileSystem> pFileSystem;
    if(pFileSystem == NULL)
        return FALSE;

    BOOL bExists = pFileSystem->IsFolderExists(szPath);

    if(!bExists)
        return FALSE;

    return TRUE;
}
